<script lang="tsx">
import type { PropType, Ref } from 'vue';
import { computed, defineComponent, toRefs, unref } from 'vue';
import type { FormActionType, FormProps, FormSchema } from '../types/form';
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { TableActionType } from '@/components/Table';
import { Col, Divider, Form, FormItem, Row, Select } from 'ant-design-vue';
import { componentMap } from '../componentMap';
import { BasicHelp } from '@/components/Basic';
import { isBoolean, isFunction, isNull } from '@/utils/is';
import { getSlot } from '@/utils/helper/tsxHelper';
import { useGo } from '@/hooks/web/usePage';
import {
  createPlaceholderMessage,
  NO_AUTO_LINK_COMPONENTS,
  setComponentRuleType,
} from '../helper';
import { cloneDeep, upperFirst } from 'lodash-es';
import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { useI18n } from '@/hooks/web/useI18n';
import { DefaultOptionType } from 'ant-design-vue/lib/select';
import { CollapseContainer } from '@/components/Container';
import ViewStyleWrapper from './viewWrapper/ViewStyleWrapper.vue';

export default defineComponent({
  name: 'BasicFormItem',
  inheritAttrs: false,
  props: {
    schema: {
      type: Object as PropType<FormSchema>,
      default: () => ({}),
    },
    formProps: {
      type: Object as PropType<FormProps>,
      default: () => ({}),
    },
    allDefaultValues: {
      type: Object as PropType<Recordable>,
      default: () => ({}),
    },
    formModel: {
      type: Object as PropType<Recordable>,
      default: () => ({}),
    },
    setQuerySchema: {
      type: Function as PropType<
        (value: Object, option: DefaultOptionType | DefaultOptionType[]) => void
      >,
      default: null,
    },
    setFormModel: {
      type: Function as PropType<
        (key: string, value: any, schema: FormSchema) => void
      >,
      default: null,
    },
    tableAction: {
      type: Object as PropType<TableActionType>,
    },
    formActionType: {
      type: Object as PropType<FormActionType>,
    },
    isAdvanced: {
      type: Boolean,
    },
  },
  setup(props, { slots }) {
    const { t } = useI18n();
    const go = useGo();
    const { schema, formProps } = toRefs(props) as {
      schema: Ref<FormSchema>;
      formProps: Ref<FormProps>;
    };
    const itemLabelWidthProp = useItemLabelWidth(schema, formProps);

    const getValues = computed(() => {
      const { allDefaultValues, formModel, schema } = props;
      const { mergeDynamicData } = props.formProps;
      return {
        field: schema.field,
        model: formModel,
        values: {
          ...mergeDynamicData,
          ...allDefaultValues,
          ...formModel,
        } as Recordable,
        schema: schema,
      };
    });

    const getComponentsProps = computed(() => {
      const { schema, tableAction, formModel, formActionType } = props;
      let { componentProps = {} } = schema;
      if (isFunction(componentProps)) {
        componentProps =
          componentProps({ schema, tableAction, formModel, formActionType }) ??
          {};
      }
      if (schema.component === 'Divider') {
        componentProps = Object.assign(
          { type: 'horizontal' },
          {
            orientation: 'left',
            plain: true,
          },
          componentProps,
        );
      }
      componentProps.viewMode =
        componentProps.viewMode || unref(formProps).mode;
      return componentProps as Recordable;
    });

    const getDisable = computed(() => {
      const { disabled: globDisabled } = props.formProps;
      const { dynamicDisabled } = props.schema;
      const { disabled: itemDisabled = false } = unref(getComponentsProps);
      let disabled = !!globDisabled || itemDisabled;
      if (isBoolean(dynamicDisabled)) {
        disabled = dynamicDisabled;
      }
      if (isFunction(dynamicDisabled)) {
        disabled = dynamicDisabled(unref(getValues));
      }
      return disabled;
    });

    function getShow(): { isShow: boolean; isIfShow: boolean } {
      const { show, ifShow } = props.schema;
      const { showAdvancedButton } = props.formProps;
      const itemIsAdvanced = showAdvancedButton
        ? isBoolean(props.isAdvanced)
          ? props.isAdvanced
          : true
        : true;

      let isShow = true;
      let isIfShow = true;

      if (isBoolean(show)) {
        isShow = show;
      }
      if (isBoolean(ifShow)) {
        isIfShow = ifShow;
      }
      if (isFunction(show)) {
        isShow = show(unref(getValues));
      }
      if (isFunction(ifShow)) {
        isIfShow = ifShow(unref(getValues));
      }
      isShow = isShow && itemIsAdvanced;
      return { isShow, isIfShow };
    }

    function handleRules(): ValidationRule[] {
      const {
        rules: defRules = [],
        component,
        rulesMessageJoinLabel,
        label,
        dynamicRules,
        required,
      } = props.schema;

      if (isFunction(dynamicRules)) {
        return dynamicRules(unref(getValues)) as ValidationRule[];
      }

      let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
      const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } =
        props.formProps;

      const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
        ? rulesMessageJoinLabel
        : globalRulesMessageJoinLabel;
      const defaultMsg =
        createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;

      function validator(rule: any, value: any) {
        const msg = rule.message || defaultMsg;
        if (value === undefined || isNull(value)) {
          // 空值
          return Promise.reject(msg);
        } else if (Array.isArray(value) && value.length === 0) {
          // 数组类型
          return Promise.reject(msg);
        } else if (typeof value === 'string' && value.trim() === '') {
          // 空字符串
          return Promise.reject(msg);
        } else if (
          typeof value === 'object' &&
          Reflect.has(value, 'checked') &&
          Reflect.has(value, 'halfChecked') &&
          Array.isArray(value.checked) &&
          Array.isArray(value.halfChecked) &&
          value.checked.length === 0 &&
          value.halfChecked.length === 0
        ) {
          // 非关联选择的tree组件
          return Promise.reject(msg);
        }
        return Promise.resolve();
      }

      const getRequired = isFunction(required)
        ? required(unref(getValues))
        : required;

      /*
       * 1、若设置了required属性，又没有其他的rules，就创建一个验证规则；
       * 2、若设置了required属性，又存在其他的rules，则只rules中不存在required属性时，才添加验证required的规则
       *     也就是说rules中的required，优先级大于required
       */
      if (getRequired) {
        if (!rules || rules.length === 0) {
          rules = [{ required: getRequired, validator }];
        } else {
          const requiredIndex: number = rules.findIndex((rule) =>
            Reflect.has(rule, 'required'),
          );

          if (requiredIndex === -1) {
            rules.push({ required: getRequired, validator });
          }
        }
      }

      const requiredRuleIndex: number = rules.findIndex(
        (rule) =>
          Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
      );

      if (requiredRuleIndex !== -1) {
        const rule = rules[requiredRuleIndex];
        const { isShow } = getShow();
        if (!isShow) {
          rule.required = false;
        }
        if (component) {
          if (!Reflect.has(rule, 'type')) {
            rule.type = component === 'InputNumber' ? 'number' : 'string';
          }

          rule.message = rule.message || defaultMsg;

          if (component.includes('Input') || component.includes('Textarea')) {
            rule.whitespace = true;
          }
          const valueFormat = unref(getComponentsProps)?.valueFormat;
          setComponentRuleType(rule, component, valueFormat);
        }
      }

      // Maximum input length rule check
      const characterInx = rules.findIndex((val) => val.max);
      if (characterInx !== -1 && !rules[characterInx].validator) {
        rules[characterInx].message =
          rules[characterInx].message ||
          t('component.form.maxTip', [rules[characterInx].max] as Recordable);
      }
      return rules;
    }
    /**
     * 是否是展示组件
     */
    function isViewComponent() {
      return (
        props.schema.component.indexOf('Input') > -1 ||
        props.schema.component.indexOf('DatePicker') > -1 ||
        props.schema.component.indexOf('MonthPicker') > -1 ||
        props.schema.component.indexOf('RangePicker') > -1 ||
        props.schema.component.indexOf('WeekPicker') > -1
      );
    }
    function renderComponent() {
      const {
        renderComponentContent,
        component,
        field,
        label,
        changeEvent = 'change',
        valueField,
      } = props.schema;
      const isCheck = component && ['Switch', 'Checkbox'].includes(component);

      const eventKey = `on${upperFirst(changeEvent)}`;

      const on = {
        [eventKey]: (...args: Nullable<Recordable>[]) => {
          const [e] = args;
          if (propsData[eventKey]) {
            propsData[eventKey](...args);
          }
          const target = e ? e.target : null;
          const value = target ? (isCheck ? target.checked : target.value) : e;
          props.setFormModel(field, value, props.schema);
        },
      };
      const Comp = componentMap.get(component) as ReturnType<
        typeof defineComponent
      >;

      const { autoSetPlaceHolder, size } = props.formProps;
      const propsData: Recordable = {
        allowClear: true,
        getPopupContainer: (trigger: Element) => trigger.parentNode,
        size,
        ...unref(getComponentsProps),
        disabled: unref(getDisable),
      };

      const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
      // RangePicker place is an array
      if (isCreatePlaceholder && component !== 'RangePicker' && component) {
        propsData.placeholder =
          unref(getComponentsProps)?.placeholder ||
          createPlaceholderMessage(component) + label;
      }
      if (component === 'RangePicker') {
        propsData.placeholder = [label + '开始', label + '结束'];
      }
      propsData.codeField = field;
      propsData.formValues = unref(getValues);
      // 只要是下拉类的组件 都要指定挂载位置 防止超出滚动区域无法显示完整
      if (component.toLowerCase().indexOf('select') > -1) {
        propsData.getPopupContainer = () => document.body;
      }
      const bindValue: Recordable = {
        [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
      };
      // 跟随表单的模式变化
      propsData.viewMode = propsData.viewMode || unref(formProps).mode;

      const compAttr: Recordable = {
        ...propsData,
        ...on,
        ...bindValue,
      };
      if (!renderComponentContent) {
        return unref(formProps).mode === 'view' ||
          propsData.viewMode === 'view' ? (
          <>
            <ViewStyleWrapper
              isLink={!!props.schema.linkFunction}
              style={propsData.style}
              onClick={() =>
                props.schema.linkFunction &&
                props.schema.linkFunction({ formModel: props.formModel, go })
              }
            >
              {isViewComponent() ? (
                <span>{props.formModel[props.schema.field]}</span>
              ) : (
                <Comp {...compAttr} />
              )}
            </ViewStyleWrapper>
          </>
        ) : (
          <Comp {...compAttr} />
        );
      }
      const compSlot = isFunction(renderComponentContent)
        ? { ...renderComponentContent(unref(getValues)) }
        : {
            default: () => renderComponentContent,
          };
      return unref(formProps).mode === 'view' ||
        propsData.viewMode === 'view' ? (
        <>
          <ViewStyleWrapper
            isLink={!!props.schema.linkFunction}
            style={propsData.style}
            onClick={() =>
              props.schema.linkFunction &&
              props.schema.linkFunction({ formModel: props.formModel, go })
            }
          >
            {isViewComponent() ? (
              <span>{props.formModel[props.schema.field]}</span>
            ) : (
              <Comp {...compAttr}>{compSlot}</Comp>
            )}
          </ViewStyleWrapper>
        </>
      ) : (
        <Comp {...compAttr}>{compSlot}</Comp>
      );
    }

    function renderLabelHelpMessage() {
      const { label, helpMessage, helpComponentProps, subLabel } = props.schema;
      // const { hideLabel } = props.formProps;
      // if (hideLabel) return null;
      // if (itemHiddenLabel) {
      //   return null;
      // }
      const renderLabel = subLabel ? (
        <span>
          {label} <span class="text-secondary">{subLabel}</span>
        </span>
      ) : (
        label
      );
      const getHelpMessage = isFunction(helpMessage)
        ? helpMessage(unref(getValues))
        : helpMessage;
      if (
        !getHelpMessage ||
        (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)
      ) {
        return renderLabel;
      }
      return (
        <span>
          {renderLabel}
          <BasicHelp
            placement="top"
            class="mx-1"
            text={getHelpMessage}
            {...helpComponentProps}
          />
        </span>
      );
    }
    function renderItem() {
      const { itemProps, slot, label, render, field, suffix, component } =
        props.schema;
      const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
      const { colon } = props.formProps;

      if (component === 'Divider') {
        return (
          <Col span={24}>
            <Divider {...unref(getComponentsProps)}>
              {renderLabelHelpMessage()}
            </Divider>
          </Col>
        );
      } else if (component === 'CollapseContainer') {
        const getParams = (c) => {
          return { ...props, schema: c, formProps: props.formProps };
        };

        return (
          <CollapseContainer
            {...({
              ...unref(getComponentsProps),
              title: label,
              size: 'small',
            } as Recordable)}
            v-slots={
              slot
                ? {
                    action: (expand) =>
                      getSlot(slots, slot, expand, unref(getValues)),
                  }
                : null
            }
          >
            <Row>
              {props.schema?.children?.map((c) => {
                return (
                  <BasicFormItem
                    {...getParams(c)}
                    v-slots={slots}
                  ></BasicFormItem>
                );
              })}
            </Row>
          </CollapseContainer>
        );
      } else {
        const getContent = () => {
          return slot
            ? getSlot(slots, slot, unref(getValues))
            : render
              ? render(unref(getValues))
              : renderComponent();
        };

        const showSuffix = !!suffix;
        const getSuffix = isFunction(suffix)
          ? suffix(unref(getValues))
          : suffix;

        // TODO 自定义组件验证会出现问题，因此这里框架默认将自定义组件设置手动触发验证，如果其他组件还有此问题请手动设置autoLink=false
        if (NO_AUTO_LINK_COMPONENTS.includes(component)) {
          props.schema &&
            (props.schema.itemProps! = {
              autoLink: false,
              ...props.schema.itemProps,
            });
        }
        return (
          <Form.Item
            name={field}
            colon={colon}
            class={{ 'suffix-item': showSuffix }}
            {...(itemProps as Recordable)}
            label={renderLabelHelpMessage()}
            rules={handleRules()}
            labelCol={labelCol}
            wrapperCol={wrapperCol}
          >
            <div style="display:flex">
              <div style="flex:1;">{getContent()}</div>
              {showSuffix && <span class="suffix">{getSuffix}</span>}
            </div>
          </Form.Item>
        );
      }
    }

    return () => {
      const {
        colProps = {},
        colSlot,
        renderColContent,
        component,
      } = props.schema;
      if (!componentMap.has(component)) {
        return null;
      }
      const { baseColProps = {}, hideLabel } = props.formProps;
      const realColProps = { ...baseColProps, ...colProps };
      const { isIfShow, isShow } = getShow();
      const values = unref(getValues);

      const getContent = () => {
        return colSlot
          ? getSlot(slots, colSlot, values)
          : renderColContent
            ? renderColContent(values)
            : renderItem();
      };

      return (
        isIfShow && (
          <Col {...realColProps} v-show={isShow}>
            <div style={{ marginRight: hideLabel ? '5px' : '' }}>
              {getContent()}
            </div>
          </Col>
        )
      );
    };
  },
});
</script>
